#include "pch.hpp"
#include "D3D11Mesh.hpp"

#include "D3D11Exception.hpp"

using namespace Engine;
using namespace Engine::Video;




D3D11Mesh::D3D11Mesh(const notify_t& destroyedFn, const ID3D11DevicePtr& device, const ID3D11DeviceContextPtr& context, uint32 sizePerVertex, uint32 sizePerIndex, const StaticReflection& vertexReflection, Format::Type indexType, Primitive::Type primitiveType, Buffer::Type bufferType)
	: m_destroyedFn(destroyedFn)
	, m_device(device)
	, m_context(context)
	, m_vertexBufferSize(0)
	, m_indexBufferSize(0)
	, m_vertexReflection(vertexReflection)
	, m_indexType(indexType)
	, m_primitiveType(primitiveType)
	, m_bufferType(bufferType)

	, m_sizePerVertex(sizePerVertex)
	, m_sizePerIndex(sizePerIndex)

	, m_numVertices(0)
	, m_numIndices(0)
	, m_numPrimitives(0)
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

void D3D11Mesh::post_create(ptr _this)
{
	m_this = _this;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

D3D11Mesh::~D3D11Mesh()
{
	m_destroyedFn(m_this);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




Buffer::Type D3D11Mesh::usage() const
{
	return m_bufferType;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

Primitive::Type D3D11Mesh::primitiveType() const
{
	return m_primitiveType;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

StaticReflection D3D11Mesh::reflect() const
{
	return m_vertexReflection;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void D3D11Mesh::createVertexBuffer(const void* vertexData, uint32 vertexSize)
{
	// Skip vertex buffer creation entirely until there's data
	// to fill it with
	if(0 == vertexSize)
		return;

	// Fill in a buffer description.
	D3D11_BUFFER_DESC desc = fromUsage(m_bufferType);
	desc.ByteWidth = vertexSize;
	desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

	// Fill in the subresource data.
	D3D11_SUBRESOURCE_DATA initData;
	ZeroMemory(&initData, sizeof(initData));
	initData.pSysMem = vertexData;
	initData.SysMemPitch = 0;
	initData.SysMemSlicePitch = 0;

	// Create the vertex buffer.
	ID3D11Buffer* buff;
	D3D11_API(m_device->CreateBuffer(&desc, &initData, &buff));
	m_vertexBuffer     = ID3D11BufferPtr(buff);
	m_vertexBufferSize = vertexSize;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void D3D11Mesh::createIndexBuffer(const void* indexData, uint32 indexSize)
{
	// Skip index buffer creation entirely until there's data
	// to fill it with
	if(0 == indexSize)
		return;

	// Fill in a buffer description.
	D3D11_BUFFER_DESC desc = fromUsage(m_bufferType);
	desc.ByteWidth = indexSize;
	desc.BindFlags = D3D11_BIND_INDEX_BUFFER;

	// Define the resource data.
	D3D11_SUBRESOURCE_DATA initData;
	ZeroMemory(&initData, sizeof(initData));
	initData.pSysMem = indexData;

	// Create the buffer with the device.
	ID3D11Buffer* buff;
	D3D11_API(m_device->CreateBuffer(&desc, &initData, &buff));
	m_indexBuffer     = ID3D11BufferPtr(buff);
	m_indexBufferSize = indexSize;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void D3D11Mesh::update(const void* vertexData, uint32 vertexSize, const void* indexData, uint32 indexSize, uint32 sizePerIndex, Format::Type indexType)
{
	update_vertices(vertexData, vertexSize);
	update_indices(indexData, indexSize);

	m_sizePerIndex  = sizePerIndex;
	m_indexType     = indexType;

	m_numVertices   = vertexSize / m_sizePerVertex;
	m_numIndices    = indexSize / m_sizePerIndex;
	m_numPrimitives = calculatePrimitiveCount(m_numVertices, m_numIndices, m_primitiveType);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void D3D11Mesh::update_vertices(const void* vertexData, uint32 vertexSize)
{
	if(m_bufferType == Static)
		BOOSTEXT_THROW(programming_error("Can't update a static mesh"));

	// There's nothing to do when there shouldn't be any more vertices.
	// We simply update the number of vertices & primitives in the
	// update() method.
	if(0 == vertexSize)
		return;

	if(vertexSize > m_vertexBufferSize)
	{
		// We must create a new buffer
		createVertexBuffer(vertexData, vertexSize);
	}
	else
	{
		// We can keep the old one and simply overwrite the data
		switch(m_bufferType)
		{
		case Buffer::SemiDynamic:

			D3D11_BOX box;
			ZeroMemory(&box, sizeof(box));
			box.right = vertexSize;
			box.bottom = 1;
			box.back = 1;

			m_context->UpdateSubresource(m_vertexBuffer.get(), 0, &box, vertexData, 0, 0);
			break;
		case Buffer::Dynamic:

			D3D11_MAPPED_SUBRESOURCE res;
			D3D11_API(m_context->Map(m_vertexBuffer.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &res));
			memcpy(res.pData, vertexData, vertexSize);
			m_context->Unmap(m_vertexBuffer.get(), 0);

			break;
		default:
			BOOSTEXT_THROW(invalid_value_exception(m_bufferType));
		}
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void D3D11Mesh::update_indices(const void* indexData, uint32 indexSize)
{
	if(m_bufferType == Static)
		BOOSTEXT_THROW(programming_error("Can't update a static mesh"));

	// There's nothing to do when there shouldn't be any more indices.
	// We simply update the number of vertices & primitives in the
	// update() method.
	if(0 == indexSize)
		return;

	if(indexSize > m_indexBufferSize)
	{
		// We must create a new buffer
		createIndexBuffer(indexData, indexSize);
	}
	else
	{
		// We can keep the old one and simply overwrite the data
		switch(m_bufferType)
		{
		case Buffer::SemiDynamic:

			D3D11_BOX box;
			ZeroMemory(&box, sizeof(box));
			box.right = indexSize;
			box.bottom = 1;
			box.back = 1;

			m_context->UpdateSubresource(m_indexBuffer.get(), 0, &box, indexData, 0, 0);
			break;
		case Buffer::Dynamic:

			D3D11_MAPPED_SUBRESOURCE res;
			D3D11_API(m_context->Map(m_indexBuffer.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &res));
			memcpy(res.pData, indexData, indexSize);
			m_context->Unmap(m_indexBuffer.get(), 0);

			break;
		default:
			BOOSTEXT_THROW(invalid_value_exception(m_bufferType));
		}
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void D3D11Mesh::stream(const ID3D11DeviceContextPtr& context) const
{
	// Don't do anything when there aren't any vertices
	if(0 == m_vertexBufferSize)
		return;

	// Setup the index buffer for indexed meshes
	switch(m_primitiveType)
	{
	case Primitive::IndexedTriangles:
	case Primitive::IndexedLines:
		if(m_indexBuffer)
			context->IASetIndexBuffer(m_indexBuffer.get(), formatType(m_indexType), 0);
	}

	// Setup the vertexbuffer
	UINT stride = m_sizePerVertex;
	UINT offset = 0;
	ID3D11Buffer* vertexBuffer[1];
	vertexBuffer[0] = m_vertexBuffer.get();
	context->IASetVertexBuffers(0, 1, vertexBuffer, &stride, &offset);

	context->IASetPrimitiveTopology(topologyFromPrimitive(m_primitiveType));
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void D3D11Mesh::draw(const ID3D11DeviceContextPtr& context) const
{
	// Don't do anything when there aren't any vertices
	if(0 == m_vertexBufferSize)
		return;

	// Draw it
	switch(m_primitiveType)
	{
	case Primitive::IndexedTriangles:
	case Primitive::IndexedLines:
		context->DrawIndexed(m_numIndices, 0, 0);
		break;
	case Primitive::UnindexedTriangles:
	case Primitive::UnindexedLines:
		context->Draw(m_numVertices, 0);
		break;
	default: BOOSTEXT_THROW(invalid_value_exception(m_primitiveType));
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////




uint32 D3D11Mesh::sizePerVertex() const
{
	return m_sizePerVertex;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

uint32 D3D11Mesh::sizePerIndex() const
{
	return m_sizePerIndex;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




D3D11_BUFFER_DESC D3D11Mesh::fromUsage(Type type)
{
	D3D11_BUFFER_DESC desc;
	ZeroMemory(&desc, sizeof(desc));

	switch(type)
	{
	case Static:
		desc.Usage          = D3D11_USAGE_IMMUTABLE;
		break;
	case SemiDynamic:
		desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
		desc.Usage          = D3D11_USAGE_DEFAULT;
		break;
	case Dynamic:
		desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
		desc.Usage          = D3D11_USAGE_DYNAMIC;
		break;
	default:
		BOOSTEXT_THROW(invalid_value_exception(type));
	}

	return desc;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

DXGI_FORMAT D3D11Mesh::formatType(Format::Type type)
{
	switch(type)
	{
	/*case Format::UNKNOWN: return DXGI_FORMAT_UNKNOWN;
	case Format::R32G32B32A32_TYPELESS: return DXGI_FORMAT_R32G32B32A32_TYPELESS;*/
	case Format::R32G32B32A32_FLOAT: return DXGI_FORMAT_R32G32B32A32_FLOAT;
	case Format::R32G32B32A32_UINT: return DXGI_FORMAT_R32G32B32A32_UINT;
	case Format::R32G32B32A32_SINT: return DXGI_FORMAT_R32G32B32A32_SINT;
	/*case Format::R32G32B32_TYPELESS: return DXGI_FORMAT_R32G32B32_TYPELESS;*/
	case Format::R32G32B32_FLOAT: return DXGI_FORMAT_R32G32B32_FLOAT;
	case Format::R32G32B32_UINT: return DXGI_FORMAT_R32G32B32_UINT;
	case Format::R32G32B32_SINT: return DXGI_FORMAT_R32G32B32_SINT;
	/*case Format::R16G16B16A16_TYPELESS: return DXGI_FORMAT_R16G16B16A16_TYPELESS;
	case Format::R16G16B16A16_FLOAT: return DXGI_FORMAT_R16G16B16A16_FLOAT;
	case Format::R16G16B16A16_UNORM: return DXGI_FORMAT_R16G16B16A16_UNORM;
	case Format::R16G16B16A16_UINT: return DXGI_FORMAT_R16G16B16A16_UINT;
	case Format::R16G16B16A16_SNORM: return DXGI_FORMAT_R16G16B16A16_SNORM;
	case Format::R16G16B16A16_SINT: return DXGI_FORMAT_R16G16B16A16_SINT;
	case Format::R32G32_TYPELESS: return DXGI_FORMAT_R32G32_TYPELESS;*/
	case Format::R32G32_FLOAT: return DXGI_FORMAT_R32G32_FLOAT;
	case Format::R32G32_UINT: return DXGI_FORMAT_R32G32_UINT;
	case Format::R32G32_SINT: return DXGI_FORMAT_R32G32_SINT;
	/*case Format::R32G8X24_TYPELESS: return DXGI_FORMAT_R32G8X24_TYPELESS;
	case Format::D32_FLOAT_S8X24_UINT: return DXGI_FORMAT_D32_FLOAT_S8X24_UINT;
	case Format::R32_FLOAT_X8X24_TYPELESS: return DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS;
	case Format::X32_TYPELESS_G8X24_UINT: return DXGI_FORMAT_X32_TYPELESS_G8X24_UINT;
	case Format::R10G10B10A2_TYPELESS: return DXGI_FORMAT_R10G10B10A2_TYPELESS;
	case Format::R10G10B10A2_UNORM: return DXGI_FORMAT_R10G10B10A2_UNORM;
	case Format::R10G10B10A2_UINT: return DXGI_FORMAT_R10G10B10A2_UINT;
	case Format::R11G11B10_FLOAT: return DXGI_FORMAT_R11G11B10_FLOAT;
	case Format::R8G8B8A8_TYPELESS: return DXGI_FORMAT_R8G8B8A8_TYPELESS;
	case Format::R8G8B8A8_UNORM: return DXGI_FORMAT_R8G8B8A8_UNORM;
	case Format::R8G8B8A8_UNORM_SRGB: return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
	case Format::R8G8B8A8_UINT: return DXGI_FORMAT_R8G8B8A8_UINT;
	case Format::R8G8B8A8_SNORM: return DXGI_FORMAT_R8G8B8A8_SNORM;
	case Format::R8G8B8A8_SINT: return DXGI_FORMAT_R8G8B8A8_SINT;
	case Format::R16G16_TYPELESS: return DXGI_FORMAT_R16G16_TYPELESS;
	case Format::R16G16_FLOAT: return DXGI_FORMAT_R16G16_FLOAT;
	case Format::R16G16_UNORM: return DXGI_FORMAT_R16G16_UNORM;
	case Format::R16G16_UINT: return DXGI_FORMAT_R16G16_UINT;
	case Format::R16G16_SNORM: return DXGI_FORMAT_R16G16_SNORM;
	case Format::R16G16_SINT: return DXGI_FORMAT_R16G16_SINT;
	case Format::R32_TYPELESS: return DXGI_FORMAT_R32_TYPELESS;
	case Format::D32_FLOAT: return DXGI_FORMAT_D32_FLOAT;*/
	case Format::R32_FLOAT: return DXGI_FORMAT_R32_FLOAT;
	case Format::R32_UINT: return DXGI_FORMAT_R32_UINT;
	case Format::R32_SINT: return DXGI_FORMAT_R32_SINT;
	/*case Format::R24G8_TYPELESS: return DXGI_FORMAT_R24G8_TYPELESS;
	case Format::D24_UNORM_S8_UINT: return DXGI_FORMAT_D24_UNORM_S8_UINT;
	case Format::R24_UNORM_X8_TYPELESS: return DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
	case Format::X24_TYPELESS_G8_UINT: return DXGI_FORMAT_X24_TYPELESS_G8_UINT;
	case Format::R8G8_TYPELESS: return DXGI_FORMAT_R8G8_TYPELESS;
	case Format::R8G8_UNORM: return DXGI_FORMAT_R8G8_UNORM;
	case Format::R8G8_UINT: return DXGI_FORMAT_R8G8_UINT;
	case Format::R8G8_SNORM: return DXGI_FORMAT_R8G8_SNORM;
	case Format::R8G8_SINT: return DXGI_FORMAT_R8G8_SINT;
	case Format::R16_TYPELESS: return DXGI_FORMAT_R16_TYPELESS;
	case Format::R16_FLOAT: return DXGI_FORMAT_R16_FLOAT;
	case Format::D16_UNORM: return DXGI_FORMAT_D16_UNORM;
	case Format::R16_UNORM: return DXGI_FORMAT_R16_UNORM;*/
	case Format::R16_UINT: return DXGI_FORMAT_R16_UINT;
	/*case Format::R16_SNORM: return DXGI_FORMAT_R16_SNORM;
	case Format::R16_SINT: return DXGI_FORMAT_R16_SINT;
	case Format::R8_TYPELESS: return DXGI_FORMAT_R8_TYPELESS;
	case Format::R8_UNORM: return DXGI_FORMAT_R8_UNORM;
	case Format::R8_UINT: return DXGI_FORMAT_R8_UINT;
	case Format::R8_SNORM: return DXGI_FORMAT_R8_SNORM;
	case Format::R8_SINT: return DXGI_FORMAT_R8_SINT;
	case Format::A8_UNORM: return DXGI_FORMAT_A8_UNORM;
	case Format::R1_UNORM: return DXGI_FORMAT_R1_UNORM;
	case Format::R9G9B9E5_SHAREDEXP: return DXGI_FORMAT_R9G9B9E5_SHAREDEXP;
	case Format::R8G8_B8G8_UNORM: return DXGI_FORMAT_R8G8_B8G8_UNORM;
	case Format::G8R8_G8B8_UNORM: return DXGI_FORMAT_G8R8_G8B8_UNORM;
	case Format::BC1_TYPELESS: return DXGI_FORMAT_BC1_TYPELESS;
	case Format::BC1_UNORM: return DXGI_FORMAT_BC1_UNORM;
	case Format::BC1_UNORM_SRGB: return DXGI_FORMAT_BC1_UNORM_SRGB;
	case Format::BC2_TYPELESS: return DXGI_FORMAT_BC2_TYPELESS;
	case Format::BC2_UNORM: return DXGI_FORMAT_BC2_UNORM;
	case Format::BC2_UNORM_SRGB: return DXGI_FORMAT_BC2_UNORM_SRGB;
	case Format::BC3_TYPELESS: return DXGI_FORMAT_BC3_TYPELESS;
	case Format::BC3_UNORM: return DXGI_FORMAT_BC3_UNORM;
	case Format::BC3_UNORM_SRGB: return DXGI_FORMAT_BC3_UNORM_SRGB;
	case Format::BC4_TYPELESS: return DXGI_FORMAT_BC4_TYPELESS;
	case Format::BC4_UNORM: return DXGI_FORMAT_BC4_UNORM;
	case Format::BC4_SNORM: return DXGI_FORMAT_BC4_SNORM;
	case Format::BC5_TYPELESS: return DXGI_FORMAT_BC5_TYPELESS;
	case Format::BC5_UNORM: return DXGI_FORMAT_BC5_UNORM;
	case Format::BC5_SNORM: return DXGI_FORMAT_BC5_SNORM;
	case Format::B5G6R5_UNORM: return DXGI_FORMAT_B5G6R5_UNORM;
	case Format::B5G5R5A1_UNORM: return DXGI_FORMAT_B5G5R5A1_UNORM;
	case Format::B8G8R8A8_UNORM: return DXGI_FORMAT_B8G8R8A8_UNORM;
	case Format::B8G8R8X8_UNORM: return DXGI_FORMAT_B8G8R8X8_UNORM;
	case Format::R10G10B10_XR_BIAS_A2_UNORM: return DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM;
	case Format::B8G8R8A8_TYPELESS: return DXGI_FORMAT_B8G8R8A8_TYPELESS;
	case Format::B8G8R8A8_UNORM_SRGB: return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;
	case Format::B8G8R8X8_TYPELESS: return DXGI_FORMAT_B8G8R8X8_TYPELESS;
	case Format::B8G8R8X8_UNORM_SRGB: return DXGI_FORMAT_B8G8R8X8_UNORM_SRGB;
	case Format::BC6H_TYPELESS: return DXGI_FORMAT_BC6H_TYPELESS;
	case Format::BC6H_UF16: return DXGI_FORMAT_BC6H_UF16;
	case Format::BC6H_SF16: return DXGI_FORMAT_BC6H_SF16;
	case Format::BC7_TYPELESS: return DXGI_FORMAT_BC7_TYPELESS;
	case Format::BC7_UNORM: return DXGI_FORMAT_BC7_UNORM;
	case Format::BC7_UNORM_SRGB: return DXGI_FORMAT_BC7_UNORM_SRGB;*/
	default: BOOSTEXT_THROW(invalid_value_exception(type));
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

D3D11_PRIMITIVE_TOPOLOGY D3D11Mesh::topologyFromPrimitive(Primitive::Type type)
{
	switch(type)
	{
	case Primitive::UnindexedTriangles:
	case Primitive::IndexedTriangles:
		return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
	case Primitive::UnindexedLines:
	case Primitive::IndexedLines:
		return D3D11_PRIMITIVE_TOPOLOGY_LINELIST;
	default: BOOSTEXT_THROW(invalid_value_exception(type));
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

D3D11_INPUT_ELEMENT_DESC_ARRAY D3D11Mesh::inputDescription(const StaticReflection& reflection)
{
	uint32 offset = 0;

	D3D11_INPUT_ELEMENT_DESC_ARRAY desc(new D3D11_INPUT_ELEMENT_DESC[reflection.elements()]);
	for(std::size_t i = 0; i < reflection.elements(); ++i)
	{
		offset += reflection[i].relativeOffset;

		ZeroMemory(&desc[i], sizeof(desc[i]));
		desc[i].SemanticName  = reflection[i].semanticName;
		desc[i].SemanticIndex = reflection[i].semanticIndex;
		desc[i].AlignedByteOffset = offset;
		desc[i].Format = formatType(reflection[i].format);

		offset += boost::numeric_cast<uint32>(Format::size(reflection[i].format));
	}

	return desc;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
